#include "hcpp/ptp2_log.h"

#include "hcpp_config.h"

#include "packet/packet.h"

#include "hal/interval_timer_hal.h"
#include "hal/critical_section.h"

#include <stdbool.h>
#include <stdio.h>
#include <time.h>

// remove c99 warning implicit declaration (you can use gnu99 instead)
extern int snprintf(char *str, size_t size, const char *format, ...);

typedef struct ptp2_log_date_t      ptp2_log_date_t;
struct ptp2_log_date_t {
    uint8_t     msec;
    uint8_t     sec;
    uint8_t     min;
    uint8_t     hour;
    uint8_t     day;
};

static void num2hexstr(uint32_t num, uint8_t *chr, size_t len);

static FILE *stream;

void
ptp2_log_init()
{
    stream       = stderr;
}

#if !PTP2_LOG_TIME_MSEC_HEX
static void ptp2_log_date(ptp2_log_date_t *now)
{
    critical_section_enter();
    uint32_t sys_msec = interval_timer_hal_get_second_fraction();
    uint32_t sys_sec  = interval_timer_hal_get_seconds();
    critical_section_leave();

    now->msec = sys_msec;

    // /*
    now->day  = sys_sec / (60 * 60 * 24);
    sys_sec  -= now->day * (60 * 60 * 24);
    now->hour = sys_sec / (60 * 60);
    sys_sec  -= now->hour * (60 * 60);
    now->min  = sys_sec / (60);
    sys_sec  -= now->min * (60);
    // */

    now->sec  = sys_sec;
}
#endif

void
ptp2_log_print_header(uint16_t category, uint16_t level)
{

#if PTP2_LOG_TIME_MSEC_HEX
    critical_section_enter();
    fprintf(stream, "[%08x]", (unsigned int) interval_timer_hal_get_miliseconds());
    critical_section_leave();
#else
    ptp2_log_date_t     now;

    ptp2_log_date(&now);

    // /*
    fprintf(stream, "[%04d %02d:%02d:%02d.%03d]", now.day,
                                                  now.hour,
                                                  now.min,
                                                  now.sec,
                                                  now.msec);
    // */
    /*
    fprintf(stream, "[%016d.%03d]", now.sec,
                                    now.msec);
    */
#endif

    if      (category == PTP2_LOG_MASTER)            fprintf(stream, "[MASTER          ]");
    else if (category == PTP2_LOG_PORT)              fprintf(stream, "[PORT            ]");
    else if (category == PTP2_LOG_TASK)              fprintf(stream, "[TASK            ]");
    else if (category == PTP2_LOG_TASK_QUEUE)        fprintf(stream, "[TASK QUEUE      ]");
    else if (category == PTP2_LOG_PACKET)            fprintf(stream, "[PACKET          ]");
    else if (category == PTP2_LOG_PACKET_ANALYZER)   fprintf(stream, "[PACKET ANALYZER ]");
    else if (category == PTP2_LOG_PACKET_ASSEMBLER)  fprintf(stream, "[PACKET ASSEMBLER]");
    else if (category == PTP2_LOG_NODE_TABLE)       fprintf(stream, "[NODE TABLE      ]");
    else if (category == PTP2_LOG_SHADOW_TABLE)     fprintf(stream, "[SHADOW TABLE    ]");
    else if (category == PTP2_LOG_BMC)               fprintf(stream, "[BEST MASTER CLK ]");
    else if (category == PTP2_LOG_HAL)               fprintf(stream, "[HAL             ]");

    if      (level == PTP2_LOG_ERROR)               fprintf(stream, "[ERROR  ] ");
    else if (level == PTP2_LOG_WARNING) fprintf(stream, "[WARN ] ");
    else if (level == PTP2_LOG_INFO)    fprintf(stream, "[INFO ] ");
    else if (level == PTP2_LOG_DEBUG)               fprintf(stream, "[DEBUG  ] ");
    else if (level == PTP2_LOG_DEBUG_VERBOSE)       fprintf(stream, "[VERBOSE] ");

}


void
ptp2_log_print(const char *format, ...)
{
    va_list             args;

    va_start(args, format);
    vfprintf(stream, format, args);
    fflush(stream);
    va_end(args);
}

void
ptp2_log_println(const char *format, ...)
{
    va_list             args;

    va_start(args, format);
    vfprintf(stream, format, args);
    fprintf(stream, "\n");
    fflush(stream);
    va_end(args);
}

void
ptp2_log_append(const char *format, ...)
{
    va_list             args;

    va_start(args, format);
    vfprintf(stream, format, args);
    fflush(stream);
    va_end(args);
}

void
ptp2_log_appendln(const char *format, ...)
{
    va_list             args;

    va_start(args, format);
    vfprintf(stream, format, args);
    fprintf(stream, "\n");
    fflush(stream);
    va_end(args);
}


void
ptp2_log_mac(const mac_address_t *mac, uint8_t *str)
{
    snprintf((char *) str, PTP2_LOG_MAC_LEN, "%02x:%02x:%02x:%02x:%02x:%02x",
                                             mac->addr[0], mac->addr[1], mac->addr[2],
                                             mac->addr[3], mac->addr[4], mac->addr[5]);
}


void
ptp2_log_clock_identity(const ptp2_clock_identity_t *clock_identity, uint8_t *str)
{
    snprintf((char *) str, PTP2_LOG_CLOCK_IDENTITY_LEN, "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x",
                                                        clock_identity->raw[0], clock_identity->raw[1], clock_identity->raw[2],
                                                        clock_identity->raw[3], clock_identity->raw[4], clock_identity->raw[5],
                                                        clock_identity->raw[6], clock_identity->raw[7]);
}


void
ptp2_log_ipv4(const ipv4_address_t *ipv4, uint8_t *str)
{
    snprintf((char *) str, PTP2_LOG_IPV4_LEN, "%d.%d.%d.%d", ipv4->addr[0],
                                                             ipv4->addr[1],
                                                             ipv4->addr[2],
                                                             ipv4->addr[3]);
}


void
ptp2_log_ipv6(const ipv6_address_t *ipv6, uint8_t *str)
{
    static const uint8_t    WIDTH = 4;      /* 16-bit, WIDTH=1: 4-bit */
    uint16_t                i;
    uint16_t                j;
    uint8_t                 chr[WIDTH];
    uint16_t                len = 0;
    bool                    padding;

    /* iterate over 8 blocks */
    for (i = 0; i < IPV6_ADDRESS_HW_LEN; i++) {
        /* IPv6 block is zero */
        if (ipv6->addr16[i] == 0) {
            str[len++] = '0';

        /* IPv6 block is _NOT_ zero */
        } else {
            num2hexstr(ntohs(ipv6->addr16[i]), chr, WIDTH);
            padding = true;
            for (j = 0; j < WIDTH; j++) {
                /* don't use leading zeros */
                if (padding) {
                    if (chr[j] != '0') {
                        padding = false;
                    } else {
                        continue;
                    }
                }
                str[len++] = chr[j];
            }
        }
        str[len++] = ':';
    }
    str[--len] = '\0';
}


static void
num2hexstr(uint32_t num, uint8_t *chr, size_t len)
{
    const static uint8_t HEXADECIMAL_LOWER[] = "0123456789abcdef";

    uint8_t    *reverse = &(chr[len]);
    uint16_t    i = 0;

    do {
        *--reverse = HEXADECIMAL_LOWER[num & 0x0f];
        num >>= 4;
        i++;
    } while (num);

    for (;i < len; i++) {
        *--reverse = '0';
    }
}

void
ptp2_log_raw_packet(raw_packet_t *raw_packet)
{
    uint32_t i;
    uint32_t j;

    fprintf(stream, "raw packet (size = %lu)\n", raw_packet->size);

    // for every character in the data-array
    for (i = 0; i < raw_packet->size ; i++) {

        // if one line of hex printing is complete...
        if (i != 0 && i % 16 == 0) {
            fprintf(stream, "         ");

            for (j = i - 16; j < i; j++) {

                // if its a number or alphabet
                if (raw_packet->data[j] >= 32 && raw_packet->data[j] <= 128) {
                    fprintf(stream, "%c", (unsigned char) raw_packet->data[j]);

                // otherwise print a dot
                } else {
                    fprintf(stream, ".");
                }
            }
            fprintf(stream, "\n");
        }

        if (i % 8 == 0) {
            if (i % 16 == 0) {
                fprintf(stream, "   ");
           } else {
               fprintf(stream, " ");
           }
        }
        fprintf(stream, " %02X", (unsigned int) raw_packet->data[i]);

        // print the last spaces
        if (i == raw_packet->size - 1) {

            // extra spaces
            for ( j = 0; j < 15 - i % 16; j++) {
                fprintf(stream, "   ");
            }

            // add extra space between the two 8-byte blocks
            if (15 - i % 16 >= 8) {
                fprintf(stream, " ");
            }

            fprintf(stream, "         ");

            for ( j = i - i % 16; j <= i; j++) {
                if (raw_packet->data[j] >= 32 && raw_packet->data[j] <= 128) {
                    fprintf(stream, "%c", (unsigned char) raw_packet->data[j]);
                } else {
                    fprintf(stream, ".");
                }
            }
            fprintf(stream, "\n");
        }
    }
}

/**
 * Not implemented!
 */
const char *
ptp2_log_packet_type(packet_type_t packet_type)
{
    packet_type_t filter = 0x80000000;
    while (filter != 0) {
        filter = filter >> 1;
        if (filter & packet_type) {
            switch (filter) {
                case PACKET_TYPE_ETHERNET:  return "ethernet";
                default:                    return "not defined";
            }
        }
    }

    return "not defined";
}

const char *
ptp2_log_message_type(uint8_t msg_type)
{
    switch (msg_type) {
        case PTP2_MESSAGE_TYPE_SYNC:                    return "sync";
        case PTP2_MESSAGE_TYPE_DELAY_REQ:               return "delay_req";
        case PTP2_MESSAGE_TYPE_PDELAY_REQ:              return "pdelay_req";
        case PTP2_MESSAGE_TYPE_PDELAY_RESP:             return "pdelay_resp";
        case PTP2_MESSAGE_TYPE_FOLLOW_UP:               return "follow_up";
        case PTP2_MESSAGE_TYPE_DELAY_RESP:              return "delay_resp";
        case PTP2_MESSAGE_TYPE_PDELAY_RESP_FOLLOW_UP:   return "pdelay_resp_fup";
        case PTP2_MESSAGE_TYPE_ANNOUNCE:                return "announce";
        case PTP2_MESSAGE_TYPE_SIGNALING:               return "signaling";
        case PTP2_MESSAGE_TYPE_MANAGEMENT:              return "management";
        default:                                        return "unknown_msg_type";
    }
}


const char *
ptp2_log_tlv_type(uint16_t tlv_type)
{
    switch (tlv_type) {
        case PTP2_TLV_TYPE_MANAGEMENT:                      return "management";
        case PTP2_TLV_TYPE_MANAGEMENT_ERROR_STATUS:         return "management_error_status";
        case PTP2_TLV_TYPE_ORGANIZATION_EXTENSION:          return "organization_extension";
        case PTP2_TLV_TYPE_REQUEST_UNICAST_TRANSMISSION:    return "request_unicast_transmission";
        case PTP2_TLV_TYPE_GRANT_UNICAST_TRANSMISSION:      return "grant_unicast_transmission";
        case PTP2_TLV_TYPE_CANCEL_UNICAST_TRANSMISSION:     return "cancel_unicast_transmission";
        case PTP2_TLV_TYPE_ACK_CANCEL_UNICAST_TRANSMISSION: return "ack_cancel_unicast_transmission";
        case PTP2_TLV_TYPE_PATH_TRACE:                      return "path_trace";
        case PTP2_TLV_TYPE_ALTERNATE_TIME_OFFSET_INDICATOR: return "alternate_time_offset_indicator";
        case PTP2_TLV_TYPE_AUTHENTICATION:                  return "authentication";
        case PTP2_TLV_TYPE_AUTHENTICATION_CHALLENGE:        return "authentication_challenge";
        case PTP2_TLV_TYPE_SECURITY_ASSOCIATION_UPDATE:     return "security_association_update";
        case PTP2_TLV_TYPE_CUM_FREQ_SCALE_FACTOR_OFFSET:    return "cum_freq_scale_factor_offset";
        default:                                            return "unknown_tlv_type";
    }
}

const char *
ptp2_log_port_state(ptp2_port_state_t port_state)
{
    switch (port_state) {
        case PTP2_PORT_STATE_NOT_DEFINED:   return "not defined";
        case PTP2_PORT_STATE_INITIALIZING:  return "initializing";
        case PTP2_PORT_STATE_FAULTY:        return "faulty";
        case PTP2_PORT_STATE_DISABLED:      return "disabled";
        case PTP2_PORT_STATE_LISTENING:     return "listening";
        case PTP2_PORT_STATE_PRE_MASTER:    return "pre-master";
        case PTP2_PORT_STATE_MASTER:        return "master";
        case PTP2_PORT_STATE_PASSIVE:       return "passive";
        case PTP2_PORT_STATE_UNCALIBRATED:  return "uncalibrated";
        case PTP2_PORT_STATE_SLAVE:         return "slave";
        default:                            return "unknown port state";
    }
}

